package redis

import (
	"fmt"
	"time"

	"github.com/go-redis/redis"
)

type RedisClient interface {
	Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd
	Get(key string) *redis.StringCmd
	TxPipeline() redis.Pipeliner
	GetSet(key string, value interface{}) *redis.StringCmd
	Del(keys ...string) *redis.IntCmd
	Expire(key string, expiration time.Duration) *redis.BoolCmd
	Close() error
	Decr(key string) *redis.IntCmd
	DecrBy(key string, decrement int64) *redis.IntCmd
	Incr(key string) *redis.IntCmd
	IncrBy(key string, value int64) *redis.IntCmd
	HIncrBy(key, field string, incr int64) *redis.IntCmd
	TTL(key string) *redis.DurationCmd
	HMGet(key string, fields ...string) *redis.SliceCmd
	HMSet(key string, fields map[string]interface{}) *redis.StatusCmd
	HGetAll(key string) *redis.StringStringMapCmd
	HSet(key, field string, value interface{}) *redis.BoolCmd
	HGet(key string, field string) *redis.StringCmd
	HDel(key string, fields ...string) *redis.IntCmd
	SetNX(key string, value interface{}, expiration time.Duration) *redis.BoolCmd
	LIndex(key string, index int64) *redis.StringCmd
	LInsert(key, op string, pivot, value interface{}) *redis.IntCmd
	LInsertBefore(key string, pivot, value interface{}) *redis.IntCmd
	LInsertAfter(key string, pivot, value interface{}) *redis.IntCmd
	LLen(key string) *redis.IntCmd
	LPop(key string) *redis.StringCmd
	LPush(key string, values ...interface{}) *redis.IntCmd
	LPushX(key string, value interface{}) *redis.IntCmd
	LRange(key string, start, stop int64) *redis.StringSliceCmd
	LRem(key string, count int64, value interface{}) *redis.IntCmd
	LSet(key string, index int64, value interface{}) *redis.StatusCmd
	LTrim(key string, start, stop int64) *redis.StatusCmd
	RPop(key string) *redis.StringCmd
	RPopLPush(source, destination string) *redis.StringCmd
	RPush(key string, values ...interface{}) *redis.IntCmd
	RPushX(key string, value interface{}) *redis.IntCmd
}

type VersionControlRedisClient struct {
	version string
	client  RedisClient
}

func (v VersionControlRedisClient) buildVersionKey(key string) string {
	if len(v.version) > 0 {
		return fmt.Sprintf("%s:%s", v.version, key)
	}
	return key
}

func (v VersionControlRedisClient) Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd {
	return v.client.Set(v.buildVersionKey(key), value, expiration)
}
func (v VersionControlRedisClient) Get(key string) *redis.StringCmd {
	return v.client.Get(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) TxPipeline() redis.Pipeliner {
	return v.client.TxPipeline()
}
func (v VersionControlRedisClient) GetSet(key string, value interface{}) *redis.StringCmd {
	return v.client.GetSet(v.buildVersionKey(key), value)
}
func (v VersionControlRedisClient) Del(keys ...string) *redis.IntCmd {
	var newKeys []string
	for _, key := range keys {
		newKeys = append(newKeys, v.buildVersionKey(key))
	}
	return v.client.Del(newKeys...)
}
func (v VersionControlRedisClient) Expire(key string, expiration time.Duration) *redis.BoolCmd {
	return v.client.Expire(v.buildVersionKey(key), expiration)
}
func (v VersionControlRedisClient) Close() error {
	return v.client.Close()
}
func (v VersionControlRedisClient) Decr(key string) *redis.IntCmd {
	return v.client.Decr(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) DecrBy(key string, decrement int64) *redis.IntCmd {
	return v.client.DecrBy(v.buildVersionKey(key), decrement)
}
func (v VersionControlRedisClient) Incr(key string) *redis.IntCmd {
	return v.client.Incr(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) IncrBy(key string, value int64) *redis.IntCmd {
	return v.client.IncrBy(v.buildVersionKey(key), value)
}
func (v VersionControlRedisClient) HIncrBy(key, field string, incr int64) *redis.IntCmd {
	return v.client.HIncrBy(v.buildVersionKey(key), field, incr)
}
func (v VersionControlRedisClient) TTL(key string) *redis.DurationCmd {
	return v.client.TTL(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) HMGet(key string, fields ...string) *redis.SliceCmd {
	return v.client.HMGet(v.buildVersionKey(key), fields...)
}
func (v VersionControlRedisClient) HMSet(key string, fields map[string]interface{}) *redis.StatusCmd {
	return v.client.HMSet(v.buildVersionKey(key), fields)
}
func (v VersionControlRedisClient) HGetAll(key string) *redis.StringStringMapCmd {
	return v.client.HGetAll(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) HSet(key, field string, value interface{}) *redis.BoolCmd {
	return v.client.HSet(v.buildVersionKey(key), field, value)
}
func (v VersionControlRedisClient) HGet(key string, field string) *redis.StringCmd {
	return v.client.HGet(v.buildVersionKey(key), field)
}
func (v VersionControlRedisClient) HDel(key string, fields ...string) *redis.IntCmd {
	return v.client.HDel(v.buildVersionKey(key), fields...)
}
func (v VersionControlRedisClient) SetNX(key string, value interface{}, expiration time.Duration) *redis.BoolCmd {
	return v.client.SetNX(v.buildVersionKey(key), value, expiration)
}
func (v VersionControlRedisClient) LIndex(key string, index int64) *redis.StringCmd {
	return v.client.LIndex(v.buildVersionKey(key), index)
}
func (v VersionControlRedisClient) LInsert(key, op string, pivot, value interface{}) *redis.IntCmd {
	return v.client.LInsert(v.buildVersionKey(key), op, pivot, value)
}
func (v VersionControlRedisClient) LInsertBefore(key string, pivot, value interface{}) *redis.IntCmd {
	return v.client.LInsertBefore(v.buildVersionKey(key), pivot, value)
}
func (v VersionControlRedisClient) LInsertAfter(key string, pivot, value interface{}) *redis.IntCmd {
	return v.client.LInsertAfter(v.buildVersionKey(key), pivot, value)
}
func (v VersionControlRedisClient) LLen(key string) *redis.IntCmd {
	return v.client.LLen(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) LPop(key string) *redis.StringCmd {
	return v.client.LPop(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) LPush(key string, values ...interface{}) *redis.IntCmd {
	return v.client.LPush(v.buildVersionKey(key), values...)
}
func (v VersionControlRedisClient) LPushX(key string, value interface{}) *redis.IntCmd {
	return v.client.LPushX(v.buildVersionKey(key), value)
}
func (v VersionControlRedisClient) LRange(key string, start, stop int64) *redis.StringSliceCmd {
	return v.client.LRange(v.buildVersionKey(key), start, stop)
}
func (v VersionControlRedisClient) LRem(key string, count int64, value interface{}) *redis.IntCmd {
	return v.client.LRem(v.buildVersionKey(key), count, value)
}
func (v VersionControlRedisClient) LSet(key string, index int64, value interface{}) *redis.StatusCmd {
	return v.client.LSet(v.buildVersionKey(key), index, value)
}
func (v VersionControlRedisClient) LTrim(key string, start, stop int64) *redis.StatusCmd {
	return v.client.LTrim(v.buildVersionKey(key), start, stop)
}
func (v VersionControlRedisClient) RPop(key string) *redis.StringCmd {
	return v.client.RPop(v.buildVersionKey(key))
}
func (v VersionControlRedisClient) RPopLPush(source, destination string) *redis.StringCmd {
	return v.client.RPopLPush(v.buildVersionKey(source), v.buildVersionKey(destination))
}
func (v VersionControlRedisClient) RPush(key string, values ...interface{}) *redis.IntCmd {
	return v.client.RPush(v.buildVersionKey(key), values...)
}
func (v VersionControlRedisClient) RPushX(key string, value interface{}) *redis.IntCmd {
	return v.client.RPushX(v.buildVersionKey(key), value)
}

func NewRedisClient(redisNodes []string) RedisClient {
	if len(redisNodes) == 1 {
		return newSingletonRedisClient(redisNodes[0])
	} else {
		return newClusterRedisClient(redisNodes)
	}
}

func NewVersionControlRedisClient(redisNodes []string, version string) VersionControlRedisClient {
	return VersionControlRedisClient{
		version: version,
		client:  NewRedisClient(redisNodes),
	}
}
